#include "plotter.h"

#include <string>
#include <iostream>
#include <windows.h>
#include <gl/gl.h>  
#include <gl/glu.h>
#include <gl/glut.h>  

static float scale = 1.f;

void Plotter :: output(float x, float y, std :: string const& text, float scale)
{
  scale *= 0.015f;
  glPushMatrix();
  glScalef(scale, scale, scale);
  glTranslatef(x/scale, y/scale, 0);
  for (size_t i = 0; i != text.size(); ++i)
  {
    glutStrokeCharacter(GLUT_STROKE_MONO_ROMAN, text[i]);
	glTranslatef(-30, 0, 0);
  }
  glPopMatrix();
}

double objx = 0, objy = 0;
double x0 = 0, y0 = 0;
double shiftx = 0, shifty = 0;

void Plotter :: screen2world(int x, int y, double * wx, double * wy)
{
	double modelview[16], projection[16];
	int viewport[4];
	
	glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
	glGetDoublev( GL_PROJECTION_MATRIX, projection );
	glGetIntegerv( GL_VIEWPORT, viewport );
	double wz = 0;
	gluUnProject(x, y, 0, modelview, projection, viewport, wx, wy, &wz);
}

void Plotter :: mouse_move (int x, int y)
{
	Plotter :: screen2world(x, y, &objx, &objy);
}

/*void Plotter :: mouse_wheel ( int wheel, int dir, int mouseX, int mouseY )
{
	if (dir > 0)
		scale *= 1.1f;
	if (dir < 0)
		scale *= 0.9f;
}*/

void Plotter :: mouse (int button, int state, int x, int y)
{
	switch (button){
    case GLUT_LEFT_BUTTON:
		{			
			if (state == 0)
			{
				Plotter :: screen2world(x, y, &x0, &y0);
			}
			else
			{
				double x1 = 0, y1 = 0;
				Plotter :: screen2world(x, y, &x1, &y1);
				shiftx += x1 - x0;
				shifty += y1 - y0;
				int viewport[4];
				glGetIntegerv( GL_VIEWPORT, viewport );
				reshape(viewport[2], viewport[3]);
			}
		}
	  break;
	case GLUT_RIGHT_BUTTON:
		{
			double mx = 0;
			double my = 0;
			Plotter :: screen2world(x, y, &mx, &my);
			GLint viewport[4] = {0};
			glGetIntegerv( GL_VIEWPORT, viewport );
			double x1 = 0, y1 = 0, x2 = 0, y2 = 0;
			Plotter :: screen2world(viewport[0], viewport[1], &x1, &y1);
			Plotter :: screen2world(viewport[2], viewport[3], &x2, &y2);
			x1 /= scale;
			y1 /= scale;
			x2 /= scale;
			y2 /= scale;

			for (size_t i = 0; i != instance()->functions.size(); ++i)
			{
				if ((my - instance()->functions[i].f->value(mx)) < ((y1 - y2)*0.1))
				{
					Plotter :: screen2world(x, y, &objx, &objy);
				}
			}
		}
	  break;
	  //(x,y) (x',y')
	//y + (y' - y) * (x0 - x) / (x' - x)

    default:
      break;
  }
}

void Plotter :: arrow_keys ( int a_keys, int x, int y )  
{
  switch ( a_keys ) {
    case GLUT_KEY_UP:    
	  scale *= 1.1f;
      //glutFullScreen ( ); 
      break;
    case GLUT_KEY_DOWN:          
      //glutReshapeWindow ( 500, 500 ); 
      scale *= 0.9f;
	  break;
    default:
      break;
  }
}

void Plotter :: reshape ( int w, int h )
{
    glViewport     ( 0, 0, w, h );
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluOrtho2D(- w/20.0 - shiftx, w/20.0 - shiftx, - h/20.0 + shifty, h/20.0 + shifty);
	//printf("%lf, %lf\n", shiftx, shifty);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void Plotter :: keyboard ( unsigned char key, int, int )
{
  switch ( key ) {
    case 27:      
      exit ( 0 ); 
      break;      
    default:      
      break;
  }
}

void Plotter :: display ()   
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glLoadIdentity();
	Plotter::instance()->draw();
	glutSwapBuffers ( );  
}

Plotter :: Plotter()
{
		int argc = 1;
        char c = 0;
		char *argv[] = {&c};	    
		glutInit            ( &argc, argv ); 
        glutInitDisplayMode ( GLUT_RGB | GLUT_DOUBLE );
        glutInitWindowSize  ( 500, 500 ); 
        glutInitWindowPosition ( 500, 50 );
		glutCreateWindow    ( "Lanna's OpenGL Framework" ); 
		initModel();
		glutDisplayFunc     ( &Plotter :: display );  
        glutReshapeFunc     ( &Plotter :: reshape );
		glutKeyboardFunc    ( &Plotter :: keyboard );
		glutSpecialFunc     ( &Plotter :: arrow_keys );
		glutIdleFunc		( &Plotter :: display );
		glutMouseFunc       ( &Plotter :: mouse ); 
		glutPassiveMotionFunc( &Plotter :: mouse_move );
//		glutMouseWheelFunc  ( &Plotter :: mouse_wheel);//
}

Plotter :: ~Plotter ()
{
}

Plotter* Plotter :: instance()
{
	static Plotter p;
    return &p;
}

void Plotter :: initModel()     
{
    glShadeModel(GL_SMOOTH);	
	glClearColor(1.0f, 1.0f, 1.0f, 0.5f);
	glClearDepth(1.0f);					
	glEnable(GL_DEPTH_TEST);			
	glDepthFunc(GL_LEQUAL);				
    glEnable ( GL_COLOR_MATERIAL );
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
 }

void Plotter :: run()
{
	glutMainLoop        ( );          
}
void Plotter :: draw_axes()
{
	GLint viewport[4] = {0};
	glGetIntegerv( GL_VIEWPORT, viewport );
	using std::cout;
	
	double x1 = 0, y1 = 0, x2 = 0, y2 = 0;
	Plotter :: screen2world(viewport[0], viewport[1], &x1, &y1);
	Plotter :: screen2world(viewport[2], viewport[3], &x2, &y2);

	glColor3f(0, 0, 0);
	glBegin(GL_LINES);
		glVertex3f( 0, y1, 0 );
		glVertex3f( 0, y2, 0 );
		glVertex3f( x1, 0, 0 );
		glVertex3f( x2, 0, 0 );

		glVertex3f( -0.5f, scale, 0 );
		glVertex3f( +0.5f, scale, 0 );
		glVertex3f( scale, -0.5f, 0 );
		glVertex3f( scale, 0.5f, 0 );
	glEnd();
    output(x2 - 2, 0, "x");
    output(0, y2 - 1, "y");

    output(scale, 0, "1");
    output(0, scale, "1");
}

void Plotter :: draw()
{
	char buff[1024] = {};
	sprintf(buff, "%.2f, %.2f", objx / scale, (shifty * 2 -objy) /  scale);
	draw_axes();

	GLint viewport[4] = {0};
	glGetIntegerv( GL_VIEWPORT, viewport );
	
	double x1 = 0, y1 = 0, x2 = 0, y2 = 0;

	Plotter :: screen2world(viewport[0], viewport[1], &x1, &y1);
	Plotter :: screen2world(viewport[2], viewport[3], &x2, &y2);

	output(x1 + 5, y1 + 5, buff, 0.7f);

	for (size_t i = 0; i != functions.size(); ++i)
	{
		glColor3f(functions[i].params.r, functions[i].params.g, functions[i].params.b);
		functions[i].start = x1 / scale;
		functions[i].end = x2 / scale;
		glBegin(GL_LINE_STRIP);
			float x = functions[i].start;
			while ( x < functions[i].end )
			{
				glVertex3f(scale * x, scale * functions[i].f->value(x), 0 );
				x += (x2 - x1) / (scale * 1000.0f);
			}
		glEnd();
	}

}

void Plotter :: drawFuction( Function * f, float a, float b, PlotParams const& params )
{
	functions.push_back(FunctionRec(f,a,b,params));
}
